package com.baichen;

import tech.medivh.classpy.classfile.ClassFile;
import tech.medivh.classpy.classfile.MethodInfo;
import tech.medivh.classpy.classfile.bytecode.*;
import tech.medivh.classpy.classfile.constant.ConstantClassInfo;
import tech.medivh.classpy.classfile.constant.ConstantMethodrefInfo;
import tech.medivh.classpy.classfile.constant.ConstantPool;

import java.lang.reflect.Method;
import java.util.List;

public class Thread {

    private final String threadName;
    private final JvmStack jvmStack;
    private final PcRegister pcRegister;
    private final BootstrapClassLoader bootstrapClassLoader;

    public Thread(String threadName, StackFrame mainFrame,BootstrapClassLoader bootstrapClassLoader) {
        this.threadName = threadName;
        this.jvmStack = new JvmStack();
        jvmStack.push(mainFrame);
        this.pcRegister = new PcRegister(jvmStack);
        this.bootstrapClassLoader = bootstrapClassLoader;
    }

    public void start() throws Exception {
        for (Instruction instruction : pcRegister) {
            ConstantPool constantPool = jvmStack.peek().constantPool;
//            System.out.println(instruction);
            switch (instruction.getOpcode()) {
                case iload_0 -> jvmStack.peek().pushObjectToOperandStack(jvmStack.peek().localVariables[0]);
                case iload_1 -> jvmStack.peek().pushObjectToOperandStack(jvmStack.peek().localVariables[1]);
                case istore_0 -> {
                    Object value = jvmStack.peek().popObjectFromOperandStack();
                    jvmStack.peek().localVariables[0] = value;
                }
                case istore_1 -> {
                    Object value = jvmStack.peek().popObjectFromOperandStack();
                    jvmStack.peek().localVariables[1] = value;
                }
                case getstatic -> {
                    GetStatic getStatic = (GetStatic) instruction;
                    String className = getStatic.getClassName(constantPool);
                    String fieldName = getStatic.getFieldName(constantPool);
                    Object staticField;
                    if (className.contains("java")) {
                        // 包名包含Java的类直接调用反射加载
                        Class<?> aClass = Class.forName(className);
                        staticField = aClass.getDeclaredField(fieldName).get(null);
                        jvmStack.peek().pushObjectToOperandStack(staticField);
                    }
                }
                case iconst_1 -> jvmStack.peek().pushObjectToOperandStack(1);
                case iconst_2 -> jvmStack.peek().pushObjectToOperandStack(2);
                case iconst_3 -> jvmStack.peek().pushObjectToOperandStack(3);
                case iconst_4 -> jvmStack.peek().pushObjectToOperandStack(4);
                case iconst_5 -> jvmStack.peek().pushObjectToOperandStack(5);
                case bipush -> {
                    Bipush bipush = (Bipush) instruction;
                    int pushByte = bipush.getPushByte();
                    jvmStack.peek().pushObjectToOperandStack(pushByte);
                }
                case iadd -> {
                    int value2 = (int) jvmStack.peek().popObjectFromOperandStack();
                    int value1 = (int) jvmStack.peek().popObjectFromOperandStack();
                    jvmStack.peek().pushObjectToOperandStack(value1 + value2);
                }
                case isub -> {
                    int value2 = (int) jvmStack.peek().popObjectFromOperandStack();
                    int value1 = (int) jvmStack.peek().popObjectFromOperandStack();
                    jvmStack.peek().pushObjectToOperandStack(value1 - value2);
                }
                case invokevirtual -> {
                    InvokeVirtual invokeVirtual = (InvokeVirtual) instruction;
                    ConstantMethodrefInfo constantMethodrefInfo = invokeVirtual.getMethodInfo(constantPool);
                    String className = constantMethodrefInfo.className(constantPool);
                    String methodName = constantMethodrefInfo.methodName(constantPool);
                    List<String> params = constantMethodrefInfo.paramClassName(constantPool);

                    if (className.contains("java")) {
                        // 包名包含Java的类直接调用反射加载
                        Class<?> aClass = Class.forName(className);
                        Method method =
                                aClass.getDeclaredMethod(methodName, params.stream().map(this::nameToClass).toArray(Class[]::new));
                        Object[] args = new Object[params.size()];
                        for (int i = args.length - 1; i >= 0; i--) {
                            args[i] = jvmStack.peek().popObjectFromOperandStack();
                        }

                        Object result = method.invoke(jvmStack.peek().popObjectFromOperandStack(), args);
                        if (!constantMethodrefInfo.isVoid(constantPool)) {
                            jvmStack.peek().pushObjectToOperandStack(result);
                        }
                        break;
                    }
                    ClassFile classFile = bootstrapClassLoader.loadClass(className);
                    // 目前不支持重载，直接取得第一个同名方法 TODO
                    MethodInfo methodInfo = classFile.getMethods(methodName).get(0);
                    Object[] args = new Object[params.size() + 1];
                    for (int i = args.length - 1; i >= 0; i--) {
                        args[i] = jvmStack.peek().popObjectFromOperandStack();
                    }
                    StackFrame stackFrame = new StackFrame(methodInfo, classFile.getConstantPool(), args);
                    jvmStack.push(stackFrame);
                }
                case invokestatic -> {
                    InvokeStatic invokeStatic = (InvokeStatic) instruction;
                    ConstantMethodrefInfo constantMethodrefInfo = invokeStatic.getMethodInfo(constantPool);
                    String className = constantMethodrefInfo.className(constantPool);
                    String methodName = constantMethodrefInfo.methodName(constantPool);
                    List<String> params = constantMethodrefInfo.paramClassName(constantPool);

                    if (className.contains("java")) {
                        // 包名包含Java的类直接调用反射加载
                        Class<?> aClass = Class.forName(className);
                        Method method =
                                aClass.getDeclaredMethod(methodName, params.stream().map(this::nameToClass).toArray(Class[]::new));
                        Object[] args = new Object[params.size()];
                        for (int i = args.length - 1; i >= 0; i--) {
                            args[i] = jvmStack.peek().popObjectFromOperandStack();
                        }

                        Object result = method.invoke(null, args);
                        if (!constantMethodrefInfo.isVoid(constantPool)) {
                            jvmStack.peek().pushObjectToOperandStack(result);
                        }
                        break;
                    }
                    ClassFile classFile = bootstrapClassLoader.loadClass(className);
                    // 目前不支持重载，直接取得第一个同名方法 TODO
                    MethodInfo methodInfo = classFile.getMethods(methodName).get(0);
                    Object[] args = new Object[params.size()];
                    for (int i = args.length - 1; i >= 0; i--) {
                        args[i] = jvmStack.peek().popObjectFromOperandStack();
                    }
                    StackFrame stackFrame = new StackFrame(methodInfo, classFile.getConstantPool(), args);
                    jvmStack.push(stackFrame);
                }
                case if_icmple -> {
                    int value2 = (int) jvmStack.peek().popObjectFromOperandStack();
                    int value1 = (int) jvmStack.peek().popObjectFromOperandStack();
                    if (value1 <= value2) {
                        Branch branch = (Branch) instruction;
                        jvmStack.peek().jumpTo(branch.getJumpTo());
                    }
                }
                case if_icmpgt -> {
                    int value2 = (int) jvmStack.peek().popObjectFromOperandStack();
                    int value1 = (int) jvmStack.peek().popObjectFromOperandStack();
                    if (value1 > value2) {
                        Branch branch = (Branch) instruction;
                        jvmStack.peek().jumpTo(branch.getJumpTo());
                    }
                }
                case _return -> {
                    jvmStack.pop();
                }
                case ireturn -> {
                    Object result = jvmStack.peek().popObjectFromOperandStack();
                    jvmStack.pop();
                    jvmStack.peek().pushObjectToOperandStack(result);
                }
                default -> {
                    throw new Exception("Unknown instruction: " + instruction);
                }
            }
        }
    }

    private Class<?> nameToClass(String name) {
        if (name.equals("int")) {
            return int.class;
        }
        try {
            return Class.forName(name);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}
